﻿using System.Linq;
using System.Threading.Tasks;
using System;
using Hangfire;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System.Reflection;
using System.Runtime.Loader;
using System.Diagnostics;
using System.Text;
using Hangfire_Demo.HFCustom;


namespace Hangfire_Demo.Data
{
    public class DLLService
    {
        public Dictionary<string, projectDto> projectDict = new Dictionary<string, projectDto>();
        public Dictionary<string, projectDto> TprojectDict = new Dictionary<string, projectDto>();
        string pluginDir = AppDomain.CurrentDomain.BaseDirectory + @"plugin\";//   Test.dll
        string pluginLogDir = AppDomain.CurrentDomain.BaseDirectory + @"pluginLog\";
        const string ResultFilter = "UnexpectedResult";

        public DLLService() 
        {
            foreach (var item in new[]{ pluginDir, pluginLogDir })
            {
                if (!Directory.Exists(item))
                {
                    Directory.CreateDirectory(item);
                }
            }
        }
        public Task<Device[]> GetCollectDataAsync(DateTime startDate)
        {
            var rng = new Random();
            return Task.FromResult(Enumerable.Range(1, 5).Select(index => new Device
            {
                number = 0,
            }).ToArray());
        }

        public Task<projectDto[]> GetProjectListAsync(DateTime startDate)
        {

            getProjectsFile();
            return Task.FromResult(projectDict.Values.ToArray());
        }

        public bool RefeshPlugin()
        {
            projectDict = new Dictionary<string, projectDto>();
            getProjectsFile();
            return true;
        }
        /// <summary>
        /// 单独执行一次
        /// </summary>
        /// <param name="pp"></param>
        /// <returns></returns>
        public bool ExcuteOneTest(projectDto pp)
        {
            if (projectDict.ContainsKey(pp.childProject.Name))
            {
                string dllPath = pp.dllFileObject.FullName;
                string NameSpace = Path.GetFileNameWithoutExtension(pp.dllFileObject.Name);
                string className = "projectStart";
                string funName = "init";
                string taskName = pp.childProject.Name;

                ////传递日志统一目录参数
                string logDir = Path.Combine(pluginLogDir, pp.childProject.Name);
                string[] paras = {"-console",logDir, dllPath, pp.childProject.upLoadUrl };
                //BackgroundJob.Enqueue(() => LoadByCore(dllPath, NameSpace, className, funName));
                //BackgroundJob.Enqueue(() => GetString(dllPath, NameSpace, className, funName));
                BackgroundJob.Enqueue( () => runConsole(dllPath, paras, taskName, true));
                return true;
            }
            return false;
        }

        public void SaveOne(projectDto pp)
        {
            if (projectDict.ContainsKey(pp.childProject.Name))
            {
                var theProject = projectDict[pp.childProject.Name]; 
                string content = JsonConvert.SerializeObject(theProject.childProject, Formatting.Indented);
                File.WriteAllTextAsync( theProject.childProjectConfig.FullName, content);
                
            }
        }

        public bool ExcuteOneTestByCore(projectDto pp)
        {
            if (projectDict.ContainsKey(pp.childProject.Name))
            {
                string dllPath = pp.dllFileObject.FullName;
                string NameSpace = Path.GetFileNameWithoutExtension(pp.dllFileObject.Name);
                NameSpace = "CollectAndUpload";
                string className = "ProjectStart";
                string funName = "StartOnce";
                string taskName = pp.childProject.Name;

                ////传递日志统一目录参数
                string logDir = Path.Combine(pluginLogDir, pp.childProject.Name);
                string pluginExeDir = Path.Combine(pluginDir, pp.childProject.Name);
                string[] paras = { "-console", logDir, pluginExeDir, dllPath, pp.childProject.upLoadUrl };
                //BackgroundJob.Enqueue(() => LoadByCore(dllPath, NameSpace, className, funName));
                //BackgroundJob.Enqueue(() => GetString(dllPath, NameSpace, className, funName));
                BackgroundJob.Enqueue(() => LoadByCore(dllPath, NameSpace, className, funName, paras));
                return true;
            }
            return false;
        }
        public bool AddRecurringJob()
        {
            foreach (var pp in projectDict.Values)
            {
                string dllPath = pp.dllFileObject.FullName;
                string NameSpace = Path.GetFileNameWithoutExtension(pp.dllFileObject.Name);
                string className = "projectStart";
                string funName = "init";
                string taskName = pp.childProject.Name;
                string cornExpression = TranslateCron(pp);
                ////传递日志统一目录参数
                string logDir = Path.Combine(pluginLogDir, pp.childProject.Name);
                string[] paras = { "-console", logDir, dllPath, pp.childProject.upLoadUrl};
                RecurringJob.AddOrUpdate(taskName, () => runConsole(dllPath, paras, taskName, true), cornExpression);

            }
            return true;
        }

        public void getProjectsFile()
        {
            DirectoryInfo plugDirObject = new DirectoryInfo(pluginDir);
            if (!Directory.Exists(pluginDir))
            {
                Directory.CreateDirectory(pluginDir);
            }
            DirectoryInfo[] directories = plugDirObject.GetDirectories();
            foreach (DirectoryInfo dir in directories)
            {
                var dlls = dir.GetFiles("project.config");
                if (dlls.Length > 0)
                {
                    string content = File.ReadAllText(dlls[0].FullName);
                    ChildProject cp = JsonConvert.DeserializeObject<ChildProject>(content);

                    projectDto temp = new projectDto();
                    temp.childProjectConfig = dlls[0];
                    temp.dllFileObject = dir.GetFiles(cp.dllName).FirstOrDefault();
                    temp.childProject = cp;

                    if (!projectDict.ContainsKey(cp.Name))
                    {
                        projectDict.Add(cp.Name, temp);
                    }
                }
            }
        }

        public bool AddJob(string className, string dllName, string funName, string cron)
        {
            string assemblyDir = System.AppDomain.CurrentDomain.BaseDirectory + @"plugin\";//   Test.dll
            var files = GetAllFiles(assemblyDir);
            foreach (var item in files)
            {
                if (item.FileName.Equals(dllName))
                {
                    //RecurringJob.AddOrUpdate($"{className}.{funName}", () => GetString(item.FileFullName, item.FileName,  className, funName), cron);

                }
            }
            //return Content("执行成功");
            return true;
        }

        public bool RemoveJob(string className, string dllName, string funName)
        {
            string assemblyDir = System.AppDomain.CurrentDomain.BaseDirectory + @"plugin\";//   Test.dll
            var files = GetAllFiles(assemblyDir);
            foreach (var item in files)
            {
                if (item.FileName.Equals(dllName))
                {
                    RecurringJob.RemoveIfExists($"{className}.{funName}");

                }
            }
            //return Content("删除成功");
            return true;
        }

        public List<FileDto> GetAllFiles(string parentDir)
        {

            //获取目录parentDir下的所有的文件，并过滤得到所有的文本文件
            //var files = Directory.GetFiles(parentDir, "*.dll");
            DirectoryInfo folder = new DirectoryInfo(parentDir);
            var files = folder.GetFiles("*.dll");
            List<FileDto> result = new List<FileDto>();
            foreach (var file in files)
            {
                FileDto fileDto = new FileDto();
                fileDto.FileFullName = file.FullName;
                fileDto.FileName = file.Name.Substring(0, file.Name.Length - 4);
                fileDto.FileSuffix = "dll";
                result.Add(fileDto);
            }
            return result;
        }

        /// <summary>
        /// 需要加载子程序依赖的全部程序集，只适应插件调用
        /// </summary>
        /// <param name="dllPath"></param>
        /// <param name="Namespace"></param>
        /// <param name="ClassName"></param>
        /// <param name="MethodName"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public string LoadByCore(string dllPath, string Namespace, string ClassName, string MethodName, string[] args)
        {
            string Parameter = string.Join(" ", args);
            // 加载 DLL 指定是否可以卸载
            AssemblyLoadContext context = new AssemblyLoadContext("MyLibraryContext", true);
            // 加载程序集
            Assembly assembly = context.LoadFromAssemblyPath(dllPath);

            // 使用加载的程序集
            Type myType = assembly.GetType($"{Namespace}.{ClassName}");
            dynamic myInstance = Activator.CreateInstance(myType);

            // 执行程序集中的方法
            var method = myType.GetMethod(MethodName);
            try
            {
                method.Invoke(myInstance, new object[] { args });
            }
            catch (Exception error)
            {
                return error.ToString();
            }
            
            // 卸载程序集
            context.Unload();
            return assembly.FullName;
        }

        ////由于有 retry 的机制, 所以在设计 job 时, 需要做 transaction 确保原子性, 或者把方法做成幂等,
        ////
        ////AttemptsExceededAction.Delete重试还是失败超次数，进入自动删除状态 
        ////AttemptsExceededAction.Fail  失败计入失败状态，不会自动删除，需要手动删除
        ////
        //// [JobDisplayName("控制台调用")] ////不适用 换为 注册件 处理任务显示名称
        //// [CustomApplyStateFilter]  ////这里状态过滤器拦截 未能实现 判断结果不在预期的

        [AutomaticRetry(Attempts=1, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
        public string runConsole(string exePath, string[] args, string taskName, bool includeErr = false) {
            string Parameter = string.Join(" ", args);  
            ProcessStartInfo psi = new ProcessStartInfo(exePath, Parameter);
            psi.RedirectStandardInput = true;
            psi.RedirectStandardOutput = true;
            psi.RedirectStandardError = true;
            psi.UseShellExecute = false;
            psi.CreateNoWindow = true;
            Process process = new Process();
            process.StartInfo = psi;    
            StringBuilder result = new StringBuilder();
            process.OutputDataReceived += (sender, args) =>
            {
                if (!string.IsNullOrEmpty(args.Data))
                {
                    //Console.WriteLine($"Output: {args.Data}");
                    result.AppendLine(args.Data);
                }
            };

            process.ErrorDataReceived += (sender, args) =>
            {
                if (!string.IsNullOrEmpty(args.Data))
                {
                    //Console.WriteLine($"Error: {args.Data}");
                    result.AppendLine(args.Data);
                }
            };

            process.Start();
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();
            process.WaitForExit();

            //if (process.ExitCode == 0)
            //{
            //    Console.WriteLine("External process executed successfully");

            //}
            //else
            //{
            //    Console.WriteLine("External process failed");
            //}

            if (result.ToString().Contains(ResultFilter))
            {
                throw new System.Exception($"\r\n{ResultFilter}\r\n{result}");
            }
            return result.ToString();
        }

        

        public string TranslateCron(projectDto  theProjectSetting)
        {
            string periodUnit = theProjectSetting.childProject.planPeriodUnit;
            int thePeriod = theProjectSetting.childProject.planPeriod;
            if(thePeriod <= 0 ) thePeriod = 5;  ////限制
            switch (periodUnit) {

                case "second":
                    return $"*/{thePeriod} * * * * *";
                    break;
                case "minute":
                    return Cron.MinuteInterval(thePeriod);
                    break;
                case "hour":
                    return Cron.HourInterval(thePeriod);
                    break;
                case "day":
                    return Cron.DayInterval(thePeriod);
                    break;
                case "month":
                    return Cron.MonthInterval(thePeriod);
                    break;
                default: 
                    return Cron.Minutely();
            }
        }
    }
}
